/*
 *	Load Monitor
 *
 *	Copyright © 2001 Alexandre Vial, some parts from Bernhard Baehr
 *
 *	MainController.m - Main Application Controller Class
 *
 *	This program is free software; you can redistribute it and/or modify
 *	it under the terms of the GNU General Public License as published by
 *	the Free Software Foundation; either version 2 of the License, or
 *	(at your option) any later version.
 *
 *	This program is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *	GNU General Public License for more details.
 *
 *	You should have received a copy of the GNU General Public License
 *	along with this program; if not, write to the Free Software
 *	Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */


#import "MainController.h"

#import "AboutBox.h"
#import <Carbon/Carbon.h>

#define GRAPH_SIZE	128


@implementation MainController

- (void)drawImageOnWindow
{
	[iconImage drawInRect:NSMakeRect(0, 0, NSWidth([window frame]), NSHeight([window frame]))
		fromRect:NSMakeRect(0, 0, GRAPH_SIZE, GRAPH_SIZE) operation:NSCompositeCopy
		fraction:1.0];
}


- (void)showHideWindow
{
	float	size;
	
	if ([[preferences objectForKey:SHOW_GRAPH_WINDOW_KEY] boolValue]) {
		size = [[preferences objectForKey:GRAPH_WINDOW_SIZE_KEY] floatValue];
		[window setContentSize:NSMakeSize(size, size)];
		[window orderWindow:NSWindowBelow relativeTo:[preferences windowNumber]];
		[window setLevel:([[preferences objectForKey:GRAPH_WINDOW_ON_TOP_KEY] boolValue] ?
			NSFloatingWindowLevel : NSNormalWindowLevel)];
	} else
		[window orderOut:self];
}


- (void)drawPageins:(double)load1 pageouts:(double)load2 load0:(double)load0
{
	NSString		*string;
	NSMutableDictionary	*fontAttrs;
        int y;
        NSString		*aString;
        NSSize			aSize;
        
        int			delta_coeff; // if load > 9, only display 2,4,6,... feature introduced for Leon
        
        BOOL showGauge = [[preferences objectForKey:PAGEIN_ATOP_PAGEOUT_KEY] boolValue];
                                    
	// draw paging rate into the icon image
	if ([[preferences objectForKey:SHOW_PAGING_RATE_KEY] boolValue]) {
			
                string = [NSString stringWithFormat:@"Load: "];			
			
			fontAttrs = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
				[NSFont boldSystemFontOfSize:16.0], NSFontAttributeName,
				[NSColor blackColor], NSForegroundColorAttributeName,
				nil];
			[string drawAtPoint:NSMakePoint(4.0, 106.0) withAttributes:fontAttrs];
			[fontAttrs setObject:[NSColor whiteColor] forKey:NSForegroundColorAttributeName];
			[string drawAtPoint:NSMakePoint(2.0, 108.0) withAttributes:fontAttrs];

                string = [NSString stringWithFormat:@"%.2f  %.2f  %.2f", load0, load1, load2];
                if ((load0>=10.0) || (load1>=10.0) || (load2>=10.0)) string = [NSString stringWithFormat:@"%.1f  %.1f  %.1f", load0, load1, load2]; // for such a load, it is stupid to have two digits after the point, and it would not be well displayed, because too long. Actually, it would be better to study each value separately, but is it really useful ? no
			[fontAttrs setObject:[NSColor blackColor] forKey:NSForegroundColorAttributeName];
			[string drawAtPoint:NSMakePoint(4.0, 84.0) withAttributes:fontAttrs];
			[fontAttrs setObject:[NSColor whiteColor] forKey:NSForegroundColorAttributeName];
			[string drawAtPoint:NSMakePoint(2.0, 86.0) withAttributes:fontAttrs];
			
                        [fontAttrs release];
	}
/* gauge */ if (showGauge) {               
                fontAttrs = [[NSMutableDictionary alloc] initWithObjectsAndKeys:
				[NSFont systemFontOfSize:9.0], NSFontAttributeName,
				[NSColor whiteColor], NSForegroundColorAttributeName,
				nil];

                if (LOAD_MAX > 9) delta_coeff = 2; // for leon and his busy G4
                else delta_coeff = 1;
                 
                string = [NSString stringWithFormat:@"------------------------"];
                for (y=128./LOAD_MAX * delta_coeff;y<=128.*(LOAD_MAX-1)/LOAD_MAX;y+=128./LOAD_MAX * delta_coeff){
                    [ string drawAtPoint:NSMakePoint( 10.0+(delta_coeff-1)*6, y - 6.0 ) withAttributes:fontAttrs];
                    aString = [NSString stringWithFormat:@"%.0f", y * LOAD_MAX / 128. ];
	            aSize = [aString sizeWithAttributes:fontAttrs];
                    [aString drawAtPoint:NSMakePoint( 8.0+(delta_coeff-1)*6 - aSize.width, y - 6.0 ) withAttributes:fontAttrs];
                    }
                    
                        [fontAttrs release];
/* end gauge */}

}


- (void)drawComplete
// completely redraw graphImage, put graph and pageing rate into iconImage
{	
	VMData			vmdata;
	int			x;
	float			y, yy;        
	float 			transparency = [[preferences objectForKey:TRANSPARENCY_KEY] floatValue];
	double 			pgfactor;
	double 			lastload1 = 0.0, lastload2 = 0.0;
        int 			LOAD_MIN_PREF = [[preferences objectForKey:PAGING_SCALE_MAX_KEY] intValue];
        double			my_max = LOAD_MIN_PREF; // for speed improving

        if (LOAD_MAX < LOAD_MIN_PREF){ // make sure the minimal value displayed is the one defined in the preferences
            LOAD_MAX = LOAD_MIN_PREF;    // thus does nothing or increase       
            }
            
        pgfactor = 1.0 / LOAD_MAX;

        [graphImage lockFocus];

	// erase graph image with background color
	[[[preferences objectForKey:FREE_COLOR_KEY] colorWithAlphaComponent:transparency] set];
	NSRectFill (NSMakeRect(0.0, 0.0, GRAPH_SIZE, GRAPH_SIZE));

	// draw  chronological graph into graph image
	[memInfo startIterate];
                      
	for (x = 0; [memInfo getNext:&vmdata]; x++) {
        
                if (vmdata.load0 > my_max){ // for speed improving
                                                x_max = x;
                                                my_max = vmdata.load0;
                                            } 
                if (vmdata.load1 > my_max){ // for speed improving
                                                x_max = x;
                                                my_max = vmdata.load1;
                                            }
                if (vmdata.load2 > my_max){ // for speed improving
                                                x_max = x;
                                                my_max = vmdata.load2;
                                            }
                
		y = vmdata.load0 * GRAPH_SIZE * pgfactor;
		[[[preferences objectForKey:WIRED_COLOR_KEY] colorWithAlphaComponent:transparency] set];
		[NSBezierPath strokeLineFromPoint:NSMakePoint(x, 0.0) toPoint:NSMakePoint(x, y)];
		
		if (x) {
                        y = GRAPH_SIZE * vmdata.load1 * pgfactor;
                        yy = GRAPH_SIZE * lastload1 * pgfactor;
			[[preferences objectForKey:PAGEIN_COLOR_KEY] set];
			[NSBezierPath strokeLineFromPoint:NSMakePoint(x - 1, yy) toPoint:NSMakePoint(x, y)];

                        y = GRAPH_SIZE * vmdata.load2 * pgfactor;
			yy = GRAPH_SIZE * lastload2 * pgfactor;
			[[preferences objectForKey:PAGEOUT_COLOR_KEY] set];
			[NSBezierPath strokeLineFromPoint:NSMakePoint(x - 1, yy) toPoint:NSMakePoint(x, y)];
		}
		lastload1 = vmdata.load1;
		lastload2 = vmdata.load2;
	}
        
        // transfer graph image to icon image
	[graphImage unlockFocus];
	[iconImage lockFocus];	
	[graphImage compositeToPoint:NSMakePoint(0.0, 0.0) operation:NSCompositeCopy];
	
	// draw paging rate into the icon image
	[self drawPageins:vmdata.load1 pageouts:vmdata.load2 load0:vmdata.load0];
	
	[iconImage unlockFocus];
}


- (void)drawDelta
// update graphImage (based on previous graphImage), put graph and pageing rate into iconImage
{	
	VMData			vmdata, vmdata0;
	float			y, yy;	
	float 			transparency = [[preferences objectForKey:TRANSPARENCY_KEY] floatValue];
	double 			pgfactor = 1.0 / LOAD_MAX;

        int 			x;
        int 			VISIBLE_MAX = [[preferences objectForKey:PAGING_SCALE_MAX_KEY] intValue]; // reference for comparison
        double			my_max = VISIBLE_MAX; // for speed improving
        VMData			completevmdata;
        int 			dcflag = 0; // draw complete flag
                
	[graphImage lockFocus];

	// offset the old graph image
	[graphImage compositeToPoint:NSMakePoint(-1, 0) operation:NSCompositeCopy];
	
	[[[preferences objectForKey:FREE_COLOR_KEY] colorWithAlphaComponent:transparency] set];
	NSRectFill (NSMakeRect(GRAPH_SIZE - 1, 0.0, GRAPH_SIZE, GRAPH_SIZE));
	
	[memInfo getLast:&vmdata0];
	[memInfo getCurrent:&vmdata];
	
	// draw chronological graph into graph image
	y = vmdata.load0 * GRAPH_SIZE * pgfactor;
	[[[preferences objectForKey:WIRED_COLOR_KEY] colorWithAlphaComponent:transparency] set];
	[NSBezierPath strokeLineFromPoint:NSMakePoint(GRAPH_SIZE - 1, 0.0) toPoint:NSMakePoint(GRAPH_SIZE - 1, y)];

        y = GRAPH_SIZE * vmdata.load1 * pgfactor;
        yy = GRAPH_SIZE * vmdata0.load1 * pgfactor;
	[[preferences objectForKey:PAGEIN_COLOR_KEY] set];
	[NSBezierPath strokeLineFromPoint:NSMakePoint(GRAPH_SIZE - 2, yy) toPoint:NSMakePoint(GRAPH_SIZE - 1, y)];

        y = GRAPH_SIZE * vmdata.load2 * pgfactor;
        yy = GRAPH_SIZE * vmdata0.load2 * pgfactor;

	[[preferences objectForKey:PAGEOUT_COLOR_KEY] set];
	[NSBezierPath strokeLineFromPoint:NSMakePoint(GRAPH_SIZE - 2, yy) toPoint:NSMakePoint(GRAPH_SIZE - 1, y)];

	// transfer graph image to icon image
	[graphImage unlockFocus];
	[iconImage lockFocus];
	[graphImage compositeToPoint:NSMakePoint(0, 0) operation:NSCompositeCopy];

	// draw load text into the icon image
	[self drawPageins:vmdata.load1 pageouts:vmdata.load2 load0:vmdata.load0];

	[iconImage unlockFocus];
        
// now the tricky part (for me and my poor programming knowledge)        
        if (vmdata.load0 > (double)LOAD_MAX){ // if new value higher as everything before, update complete window
                LOAD_MAX = ceil(vmdata.load0); // thus do nothing or increase
                dcflag = 1;//[self drawComplete];
        }
        else {
                x_max--; // for speed improving
                //printf("x_max : %d\n",x_max);
                if (x_max < 0){ // if the max is out of the window, look for a new max
        // this should be the right place to look for the max value displayed in the window and to adapt the scaling to it
        	[memInfo startIterate]; // Very important, without it, it does not work !!!
        	for (x = 0; [memInfo getNext:&completevmdata]; x++) { //test 
                //printf("%d ",x);
                    if (completevmdata.load0 > my_max){
                                                                        x_max = x; // for speed improving
                                                                        my_max = completevmdata.load0;
                    }
                    if (completevmdata.load1 > my_max){
                                                                        x_max = x; // for speed improving
                                                                        my_max = completevmdata.load1;
                    }
                    if (completevmdata.load2 > my_max){
                                                                        x_max = x; // for speed improving
                                                                        my_max = completevmdata.load2;
                    }
                }
                VISIBLE_MAX = ceil(my_max);
                
                if (VISIBLE_MAX < LOAD_MAX){ // this should do nothing or decrease !
                    LOAD_MAX = VISIBLE_MAX; // update LOAD_MAX before new display
                    dcflag = 1;//[self drawComplete];
                    }
                }
        }
        if (dcflag) [self drawComplete];
}


- (void)refreshIcon
// get a new sample and refresh the dock icon
{
	[memInfo refresh];
	[self drawDelta];
	[NSApp setApplicationIconImage:iconImage];
	
	if ([[preferences objectForKey:SHOW_GRAPH_WINDOW_KEY] boolValue]) {
		[window disableFlushWindow];
		[view display];
		[window enableFlushWindow];
		[window flushWindow];
	}
}


- (void)updateIcon
// only refresh the dock icon (to show new preferences settings)
{
	[self drawComplete];
	[NSApp setApplicationIconImage:iconImage];
	
	if ([[preferences objectForKey:SHOW_GRAPH_WINDOW_KEY] boolValue]) {
		[window disableFlushWindow];
		[view display];
		[window enableFlushWindow];
		[window flushWindow];
	}
}


- (void)setTimer
{
	double newInterval = 0.1 * [[preferences objectForKey:UPDATE_FREQUENCY_KEY] floatValue];

	if (timer) {
		if (fabs([timer timeInterval] - newInterval) < 0.001)
			return;		/* frequency not changed */
		[timer invalidate];
		[timer release];
	}
	timer = [NSTimer scheduledTimerWithTimeInterval:newInterval
		target:self selector:@selector(refreshIcon) userInfo:nil repeats:YES];
	[timer retain];
}


- (void)showPreferences:(id)sender
{
        [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; // we go in foreground in case of using the dock menu
	[preferences showPreferences:self];
}


- (BOOL)isLoginItem
{
	id	obj;
	
	NSString *memoryMonitorPath = [[NSBundle mainBundle] bundlePath];
	NSDictionary *loginItemDict = [NSDictionary dictionaryWithContentsOfFile:[NSString stringWithFormat:@"%@/Library/Preferences/loginwindow.plist", NSHomeDirectory()]];
	NSEnumerator *loginItemEnumerator = [[loginItemDict objectForKey:@"AutoLaunchedApplicationDictionary"] objectEnumerator];

	while ((obj = [loginItemEnumerator nextObject])) {
		if ([[obj objectForKey:@"Path"] isEqualTo:memoryMonitorPath])
			return (YES);
	}
	return (NO);
}


- (unsigned)systemVersion
// returns the system version normally retrieved with Gestalt(gestaltSystemVersion, &systemVersion)
{
	const char	*p;
	
	unsigned version = 0;
	
	for (p = [[[NSDictionary dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"]
		objectForKey:@"ProductVersion"] cString]; *p; p++) {
		if (*p != '.')
			version = (version << 4) | (*p - '0');
	}
	if (version < 0x1000)	// for 10.0, 10.1
		version <<= 4;
	return (version);
}


- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
	preferences = [[Preferences alloc] init];
	memInfo = [[MemInfo alloc] initWithCapacity:GRAPH_SIZE];
LOAD_MAX = [[preferences objectForKey:PAGING_SCALE_MAX_KEY] intValue]; // initial value
	iconImage = [[NSImage allocWithZone:[self zone]] initWithSize:NSMakeSize(GRAPH_SIZE, GRAPH_SIZE)];
	graphImage = [[NSImage allocWithZone:[self zone]] initWithSize:NSMakeSize(GRAPH_SIZE, GRAPH_SIZE)];
	[self drawComplete];

	window = [[TranslucentWindow allocWithZone:[self zone]]
		initWithContentRect:NSMakeRect(0.0, 0.0, GRAPH_SIZE, GRAPH_SIZE)
		styleMask:NSBorderlessWindowMask backing:NSBackingStoreBuffered defer:NO];
	[window setReleasedWhenClosed:NO];
	[window setBackgroundColor:[NSColor clearColor]];
	[window setFrameAutosaveName:@"LoadMonitorWindowLocation"];

	view = [[TranslucentView allocWithZone:[self zone]] initWithFrame:NSMakeRect(0.0, 0.0, GRAPH_SIZE, GRAPH_SIZE)];
	[window setContentView:view];
	[view setContentDrawer:self method:@selector(drawImageOnWindow)];
	[view setAutoresizingMask:(NSViewHeightSizable | NSViewWidthSizable)];
	[view setToolTip:@"Load Monitor"];
	
	[self showHideWindow];

	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(showHideWindow) name:PREFERENCES_CHANGED object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateIcon) name:PREFERENCES_CHANGED object:nil];
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(setTimer) name:PREFERENCES_CHANGED object:nil];

	if ([self systemVersion] < 0x1010 && [self isLoginItem])
		[NSTimer scheduledTimerWithTimeInterval:30 target:self selector:@selector(setTimer) userInfo:nil repeats:NO];
	else
		[self setTimer];
}


- (void)applicationWillTerminate:(NSNotification *)aNotification 
{
	if (timer) {
		[timer invalidate];
		[timer release];
		timer = nil;
	}
	[preferences savePreferences];
	[NSApp setApplicationIconImage:[NSImage imageNamed:@"icn_load.icns"]];	
}

/*
- (void)showAboutBox:(id)sender
{
	if (! aboutBox) {
		if ([NSBundle loadNibNamed:@"About" owner:self])
			[aboutBox center];
		else {
			NSLog (@"Failed to load About.nib");
			return;
		}
	}
	[aboutBox makeKeyAndOrderFront:nil];
}
*/
- (IBAction)showAboutBox:(id)sender
{
    if ((GetCurrentKeyModifiers() & (optionKey | rightOptionKey)) != 0)
    {
        NSRunAlertPanel(@"You found it !",@"http://www.cocoadevcentral.com is great !\n\nMacOSiX le gaulois est plus fort que WinXPus.", @"OK",NULL,NULL);
    }
    else
    {   [[NSApplication sharedApplication] activateIgnoringOtherApps:YES]; // we go in foreground in case of using the dock menu
        [[AboutBox sharedInstance] showPanel:sender];
    }
}

@end
